// AnsiScreen.cpp: implementation of the CAnsiScreen class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "AnsiTerminal.h"
#include "AnsiScreen.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CAnsiScreen::CAnsiScreen() :
	m_ptCaret(0, 0),
	m_attrs(NULL),
	m_chars(NULL),
	m_ptSavedCaret(-1, -1)
{
	SetSize	(80, 24);
	m_pEvents = NULL;
	m_changeCounter = 0;
}

CAnsiScreen::~CAnsiScreen()
{
	if(m_chars)
		release();
}

void CAnsiScreen::GotoXY(int x, int y)
{
	m_ptCaret.x = getSafeX(x);
	m_ptCaret.y = getSafeY(y);
	fireOnCaretMove();
}

void CAnsiScreen::MoveUp(int offset)
{
	m_ptCaret.y = getSafeY(m_ptCaret.y - offset);
	//setCaretY(m_ptCaret.y - offset);
	fireOnCaretMove();
}

void CAnsiScreen::MoveDown(int offset)
{
	m_ptCaret.y = getSafeY(m_ptCaret.y + offset);
	//setCaretY(m_ptCaret.y + offset);
	fireOnCaretMove();
}

void CAnsiScreen::MoveLeft(int offset)
{
	m_ptCaret.x = getSafeX(m_ptCaret.x - offset);
	fireOnCaretMove();
}

void CAnsiScreen::MoveRight(int offset)
{
	m_ptCaret.x = getSafeX(m_ptCaret.x + offset);
	fireOnCaretMove();
}

void CAnsiScreen::SaveCursor()
{
	m_ptSavedCaret = m_ptCaret;
}

void CAnsiScreen::UnsaveCursor()
{
	if(m_ptSavedCaret.x != -1)
	{
		m_ptCaret.x = getSafeX(m_ptSavedCaret.x);
		m_ptCaret.y = getSafeY(m_ptSavedCaret.y);
		fireOnCaretMove();
	}
}

void CAnsiScreen::ResetAttribute()
{
	m_curAttr.Reset();
}

void CAnsiScreen::SetForecolor(ANSI_COLOR color)
{
	m_curAttr.nForeColor = color;
}

void CAnsiScreen::SetBackcolor(ANSI_COLOR color)
{
	m_curAttr.nBackColor = color;
}

void CAnsiScreen::SetHighlight(BOOL bHighlight)
{
	m_curAttr.bHighLight = bHighlight;
}

void CAnsiScreen::SetUnderline(BOOL bUnderline)
{
	m_curAttr.bUnderLine = bUnderline;
}

void CAnsiScreen::SetBlink(BOOL bBlink)
{
	m_curAttr.bBlink = bBlink;
}

void CAnsiScreen::SetInverse(BOOL bInverse)
{
	m_curAttr.bInverse = bInverse;
}

void CAnsiScreen::SetSize(int cols, int rows)
{
	BOOL bInited = (m_chars != NULL);

	char **t_chars = 0;
	CAnsiAttribute **t_attrs = 0;
	int r;

	if(bInited)
	{
		ASSERT(FALSE);
		// alloc temp memory and save current content
		t_chars = new char*[m_size.cy];
		t_attrs = new CAnsiAttribute*[m_size.cy];
		for(r = 0; r < m_size.cy; r++)
		{
			t_chars[r] = new char[m_size.cx + 1];
			t_chars[m_size.cx] = '\x0';
			t_attrs[r] = new CAnsiAttribute[m_size.cx + 1];
			memcpy(t_chars[r], m_chars[r], m_size.cx);
			memcpy(t_attrs[r], m_attrs[r], m_size.cx * sizeof(CAnsiAttribute));
		}
		release();
	}

	/* alloc new memory */

	m_chars = new char*[rows];
	m_attrs = new CAnsiAttribute*[rows];
	m_effects = new BYTE*[rows];
	for(r = 0; r < rows; r++)
	{
		m_chars[r] = new char[cols + 1];
		m_chars[r][cols] = '\x0';
		memset(m_chars[r], 32, cols);
		
		m_attrs[r] = new CAnsiAttribute[cols + 1];

		m_effects[r] = new BYTE[cols + 1];
		memset(m_effects[r], 0, (cols + 1) * sizeof(BYTE));
	}

	if(bInited)
	{
		ASSERT(FALSE);
		// restore origional content
		int nReservedRow = min(rows, m_size.cy);
		int nReservedCol = min(cols, m_size.cx);
		for(r = 0; r < nReservedRow; r++)
		{
			memcpy(m_chars[r], t_chars[r], nReservedCol);
			memcpy(m_attrs[r], t_attrs[r], nReservedCol * sizeof(CAnsiAttribute));
		}

		// dealloc temp memory
		for(r = 0; r < m_size.cy; r++)
		{
			delete [] t_chars[r];
			delete [] t_attrs[r];
		}
		delete [] t_chars;
		delete [] t_attrs;
	}
	
	m_size.cy = rows;
	m_size.cx = cols;

	if(m_ptCaret.x > m_size.cx - 1)
	{
		m_ptCaret.x = m_size.cx - 1;
	}
	if(m_ptCaret.y > m_size.cy - 1)
	{
		m_ptCaret.y = m_size.cy - 1;
	}
}

void CAnsiScreen::PutC(char c)
{
	if(c == '\r')
	{
		m_ptCaret.x = 0;
		fireOnCaretMove();
	}
	else if(c == '\n')
	{
		//Update();
		gotoNextLine();
		fireOnCaretMove();
	}
	else if(c == VK_BACK)
	{
		MoveLeft(1);
	}
	else if(c == 0x07)
	{
		Beep(450, 15);
	}
	else
	{
		m_chars[m_ptCaret.y][m_ptCaret.x] = c;
		m_attrs[m_ptCaret.y][m_ptCaret.x] = m_curAttr;
		int oldX = m_ptCaret.x;
		fireOnChange(oldX, m_ptCaret.y, oldX, m_ptCaret.y);
		m_ptCaret.x = getSafeX(m_ptCaret.x + 1);
		fireOnCaretMove();
	}
}

void CAnsiScreen::release()
{
	for(int r = 0; r < m_size.cy; r++)
	{
		delete [] m_chars[r];
		delete [] m_attrs[r];
		delete [] m_effects[r];
	}
	delete [] m_chars;
	delete [] m_attrs;
	delete [] m_effects;
	m_chars = NULL;
	m_attrs = NULL;
}

int CAnsiScreen::getSafeX(int x)
{
	if(x < 0)
		x = 0;
	else if(x > m_size.cx - 1)
		x = m_size.cx - 1;
	return x;
}

int CAnsiScreen::getSafeY(int y)
{
	if(y < 0)
		y = 0;
	else if(y > m_size.cy - 1)
		y = m_size.cy - 1;
	return y;
}

void CAnsiScreen::Erase(EraseType type)
{
	CRect rectRange;
	int pos, pos1, pos2;
	int nCharsPerLine = m_size.cx + 1;
	switch(type)
	{
	case EraseToEOL:
		pos1 = m_ptCaret.y * nCharsPerLine + m_ptCaret.x;
		pos2 = (m_ptCaret.y + 1) * nCharsPerLine - 1;
		rectRange.SetRect(m_ptCaret.x, m_ptCaret.y, m_size.cx - 1, m_ptCaret.y);
		break;
	case EraseToBOL:
		pos1 = m_ptCaret.y * nCharsPerLine;
		pos2 = m_ptCaret.y * nCharsPerLine + m_ptCaret.x;
		rectRange.SetRect(0, m_ptCaret.y, m_ptCaret.x, m_ptCaret.y);
		break;
	case EraseLine:
		pos1 = m_ptCaret.y * nCharsPerLine;
		pos2 = (m_ptCaret.y + 1) * nCharsPerLine - 1;
		rectRange.SetRect(0, m_ptCaret.y, m_size.cx - 1, m_ptCaret.y);
		break;
	case EraseToEOS:
		pos1 = m_ptCaret.y * nCharsPerLine + m_ptCaret.x;
		pos2 = nCharsPerLine * m_size.cy - 1;
		rectRange.SetRect(0, m_ptCaret.y, m_size.cx - 1, m_size.cy - 1);
		break;
	case EraseToBOS:
		pos1 = 0;
		pos2 = m_ptCaret.y * nCharsPerLine + m_ptCaret.x;
		rectRange.SetRect(0, 0, m_size.cx - 1, m_ptCaret.y);
		break;
	case EraseScreen:
		pos1 = 0;
		pos2 = nCharsPerLine * m_size.cy - 1;
		rectRange.SetRect(0, 0, m_size.cx - 1, m_size.cy - 1);
		break;
	}
	for(pos = pos1; pos <= pos2; pos++)
	{
		int y = pos / nCharsPerLine;
		int x = pos % nCharsPerLine;

		if(x != m_size.cx)
		{
			m_chars[y][x] = char(32);
			m_attrs[y][x] = m_curAttr;
		}
	}
	fireOnChange(rectRange.left, rectRange.top, rectRange.right, rectRange.bottom);
}


CString CAnsiScreen::GetScreenText()
{
	CString result;
	for(int r = 0; r < m_size.cy; r++)
	{
		result += m_chars[r];
		result += "\r\n";
	}
	return result;
}

CSize CAnsiScreen::GetSize()
{
	return m_size;
}

CPoint CAnsiScreen::GetCaretPos()
{
	return m_ptCaret;
}

char** CAnsiScreen::GetChars()
{
	return m_chars;
}

CAnsiAttribute** CAnsiScreen::GetAttrs()
{
	return m_attrs;
}

void CAnsiScreen::RegisterEvents(IAnsiScreenEvents *pEvents)
{
	m_pEvents = pEvents;
}

void CAnsiScreen::fireOnChange(int x1, int y1, int x2, int y2, BOOL addChangeCounter/* = TRUE */)
{
	if(m_pEvents)
	{
		m_pEvents->OnChange(x1, y1, x2, y2);
	}
	if(addChangeCounter)
		++m_changeCounter;
}

void CAnsiScreen::gotoNextLine()
{
	if(m_ptCaret.y < m_size.cy - 1)
	{
		++m_ptCaret.y;
		return;
	}
	// on last line
	int lineCount = 1;
	int n;
	m_pEvents->OnScreenUp(lineCount);
	int nCharsPerLine = m_size.cx + 1;
	for(n = 0; n < m_size.cy - lineCount; n++)
	{
		memcpy(m_chars[n], m_chars[n + lineCount], nCharsPerLine * sizeof(char));
		memcpy(m_attrs[n], m_attrs[n + lineCount], m_size.cx * sizeof(CAnsiAttribute));
	}
	for(n = m_size.cy - lineCount; n < m_size.cy; n++)
	{
		memset(m_chars[n], 32, sizeof(char) * m_size.cx);
		for(int c = 0; c < m_size.cx; c++)
		{
			m_attrs[n][c].Reset();
		}
	}
	m_ptCaret.y = m_size.cy - 1;
}

void CAnsiScreen::fireOnCaretMove()
{
	if(m_pEvents)
	{
		m_pEvents->OnCaretMove();
	}
	++m_changeCounter;
}

void CAnsiScreen::SetCursor(HCURSOR hCursor)
{
	if(m_pEvents)
	{
		m_pEvents->OnSetCursor(hCursor);
	}
}

int CAnsiScreen::GetChangeCounter()
{
	return m_changeCounter;
}

void CAnsiScreen::InsertLine(int lineCount)
{
	int n;
	int nCharsPerLine = m_size.cx + 1;
	for(n = m_size.cy - 1; n >= m_ptCaret.y + lineCount; n--)
	{
		memcpy(m_chars[n], m_chars[n - lineCount], nCharsPerLine * sizeof(char));
		memcpy(m_attrs[n], m_attrs[n - lineCount], m_size.cx * sizeof(CAnsiAttribute));
	}
	for(n = m_ptCaret.y; n < m_ptCaret.y + lineCount; n++)
	{
		memset(m_chars[n], 32, sizeof(char) * m_size.cx);
		for(int c = 0; c < m_size.cx; c++)
		{
			m_attrs[n][c] = m_curAttr;
		}
	}
	m_pEvents->OnChange(0, m_ptCaret.y, m_size.cx - 1, m_size.cy - 1);
}

void CAnsiScreen::SetEffect(int x, int y, BYTE effect)
{
	ASSERT(x >= 0 && x < m_size.cx && y >= 0 && y < m_size.cy);
	m_effects[y][x] = effect;
	fireOnChange(x, y, x, y, FALSE);
}

BYTE CAnsiScreen::GetEffect(int x, int y)
{
	ASSERT(x >= 0 && x < m_size.cx && y >= 0 && y < m_size.cy);
	return m_effects[y][x];
}

BYTE** CAnsiScreen::GetEffects()
{
	return m_effects;
}

void CAnsiScreen::Update()
{
	if(m_pEvents)
	{
		m_pEvents->OnFlushChange();
	}
}